package com.jinke.api.common.util;

import lombok.extern.slf4j.Slf4j;

import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.*;

/**
 * rsa 加密工具
 */
@Slf4j
public class RSAUtils {

    public static final String CHARSET = "UTF-8";

    // 加密算法
    private final static String ALGORITHM_RSA = "RSA";

    //公钥
    public static final String PUBLIC_KEY = "dxjj-publicKey";

    //私钥
    public static final String PRIVATE_KEY = "dxjj-privateKey";
    // 加密公钥
    public static final String ENCRYPT_PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCAE4gv+F+/no44YYTn94fk4vjIA/KFE7+VSnLfCrXoe44OqY4ZVmqpVSr13Yq1K3YlWVrevDqBKnrlXfy0tbnmcbeknWCmbJhcqe3rjviUC0ofxVaAQ5Kj+W/OPAwOgSAwAoH6jYIJfuK4Fyq7yun3W/rb2bcg0yH555Wmt87BwQIDAQAB";
    // 加密私钥
    public static final String ENCRYPT_PRIVATE_KEY = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAIATiC/4X7+ejjhhhOf3h+Ti+MgD8oUTv5VKct8Kteh7jg6pjhlWaqlVKvXdirUrdiVZWt68OoEqeuVd/LS1ueZxt6SdYKZsmFyp7euO+JQLSh/FVoBDkqP5b848DA6BIDACgfqNggl+4rgXKrvK6fdb+tvZtyDTIfnnlaa3zsHBAgMBAAECgYA5Jv4l0eMAQkPauwGw9c+JfxNfRguqANcESrWZ/FCSjtgkiNslSV/Ncnj6QENOdzeAkTAwprRAwXEX+2nyRgnPPsQumBGgicCAI6tS2Xxz1mBgyhxm7sJCegkQaDKNU0SN0dOQt5HkjCRycDL0jK/TrKD8grGfsYSj+LuIg2FrhQJBAMG6oWikOvPf8pYlTrKe3k9fQcJg0vztZ2UNHfdMkcIijnVIJ+mKjTQGM4QWyFjDWOzvQ8hTaVA+1nYHqeqbesMCQQCpPooAQYxqu1JPRy1OXSWxy96BmMhtpmPaxwlMqWE56QqwLzwjWl/RCYGnZoI4hbuSFI53zkw1P5TPAQaNyiErAkEAsYbs0o9s1n6qNp0JePXMkr+f6xf/QHfs/9T9WNl52IUCNsdgi8rDiqEEJE8cWPrqSnUXkEOfhkGPFaoAM1U3MQJAJEPVWEazau9mrLwotiD5gcaYkBaH5A8CWI/Lq1SkE7+e3a/RaBXmcClkUdWCU9A8KAolmQBojgRow46Js/o67wJBAJ76+p/VLdVSiovkhImgY5aRE2CSbe2FLxhvDPVMHl8RhBqdAiTxQLYqocVyLCjqRT3ZdSnYYaYLI8vEGLc3XIQ=";
    /**
     * @Author: hhp
     * @Description: 直接生成公钥、私钥对象
     * @Data: 2023/10/23 15:01
     * @param int modulus
     * @return: List<Key>
     * @throws: NoSuchAlgorithmException
     **/
    public static List<Key> getRSAKeyObject(int modulus) throws NoSuchAlgorithmException {
        List<Key> keyList = new ArrayList<>(2);
        // 创建RSA密钥生成器
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(ALGORITHM_RSA);
        // 设置密钥的大小，此处是RSA算法的模长 = 最大加密数据的大小
        keyPairGen.initialize(modulus);
        KeyPair keyPair = keyPairGen.generateKeyPair();
        // keyPair.getPublic() 生成的是RSAPublic的是咧
        keyList.add(keyPair.getPublic());
        // keyPair.getPrivate() 生成的是RSAPrivateKey的实例
        keyList.add(keyPair.getPrivate());
        return keyList;
    }

    /**
     * @Author: hhp
     * @Description: 生成公钥、私钥的字符串
     * @Data: 2023/10/23 15:03
     * @param int modulus 模长
     * @return: java.util.Map<java.lang.String,java.lang.String>
     * @throws:
     **/
    public static Map<String, String> getRSAKeyString(int modulus) throws NoSuchAlgorithmException {
        // map装载公钥和私钥
        Map<String, String> keyPairMap = new HashMap<String, String>();
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(ALGORITHM_RSA);
        keyPairGen.initialize(modulus);
        KeyPair keyPair = keyPairGen.generateKeyPair();
        String publicKey = Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded());
        String privateKey = Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded());
        keyPairMap.put(PUBLIC_KEY, publicKey);
        keyPairMap.put(PRIVATE_KEY, privateKey);
        return keyPairMap;
    }

    /**
     * @Author: hhp
     * @Description: Java中RSAPublicKeySpec、X509EncodedKeySpec支持生成RSA公钥此处使用X509EncodedKeySpec生成
     * @Data: 2023/10/23 15:03
     * @param java.lang.String publicKey
     * @return: java.security.interfaces.RSAPublicKey
     * @throws: Exception
     **/
    public static RSAPublicKey getPublicKey(String publicKey) throws Exception {
        KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM_RSA);
        byte[] keyBytes = Base64.getDecoder().decode(publicKey);
        X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
        return (RSAPublicKey) keyFactory.generatePublic(spec);
    }

    /**
     * @Author: hhp
     * @Description: Java中只有RSAPrivateKeySpec、PKCS8EncodedKeySpec支持生成RSA私钥此处使用PKCS8EncodedKeySpec生成
     * @Data: 2023/10/23 15:04
     * @param java.lang.String privateKey
     * @return: java.security.interfaces.RSAPrivateKey
     * @throws:
     **/
    public static RSAPrivateKey getPrivateKey(String privateKey) throws Exception {
        KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM_RSA);
        byte[] keyBytes = Base64.getDecoder().decode(privateKey);
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
        return (RSAPrivateKey) keyFactory.generatePrivate(spec);
    }

    /**
     * @Author: hhp
     * @Description: 公钥加密
     * @Data: 2023/10/23 15:04
     * @param java.lang.String data
     * @param java.security.interfaces.RSAPublicKey publicKey
     * @return: java.lang.String
     * @throws:
     **/
    public static String encryptByPublicKey(String data, RSAPublicKey publicKey)
            throws Exception {
        Cipher cipher = Cipher.getInstance(ALGORITHM_RSA);
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        // 模长n转换成字节数
        int modulusSize = publicKey.getModulus().bitLength() / 8;
        // PKCS Padding长度为11字节，所以实际要加密的数据不能要 - 11byte
        int maxSingleSize = modulusSize - 11;
        // 切分字节数组，每段不大于maxSingleSize
        byte[][] dataArray = splitArray(data.getBytes(CHARSET), maxSingleSize);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        // 分组加密，并将加密后的内容写入输出字节流
        for (byte[] s : dataArray) {
            out.write(cipher.doFinal(s));
        }
        // 使用Base64将字节数组转换String类型
        return Base64.getEncoder().encodeToString(out.toByteArray());
    }

    /**
     * @Author: hhp
     * @Description: 私钥解密
     * @Data: 2023/10/23 15:04
     * @param java.lang.String data
     * @param java.security.interfaces.RSAPrivateKey privateKey
     * @return: java.lang.String
     * @throws:
     **/
    public static String decryptByPrivateKey(String data, RSAPrivateKey privateKey)
            throws Exception {
        Cipher cipher = Cipher.getInstance(ALGORITHM_RSA);
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        // RSA加密算法的模长 n
        int modulusSize = privateKey.getModulus().bitLength() / 8;
        byte[] dataBytes = data.getBytes(CHARSET);
        // 之前加密的时候做了转码，此处需要使用Base64进行解码
        byte[] decodeData = Base64.getDecoder().decode(dataBytes);
        // 切分字节数组，每段不大于modulusSize
        byte[][] splitArrays = splitArray(decodeData, modulusSize);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        for (byte[] arr : splitArrays) {
            out.write(cipher.doFinal(arr));
        }
        return new String(out.toByteArray());
    }

    /**
     * @Author: hhp
     * @Description: 公钥加密
     * @Data: 2023/10/23 15:05
     * @param java.lang.String data
     * @param java.security.interfaces.RSAPrivateKey privateKey
     * @return: java.lang.String
     * @throws:
     **/
    public static String encryptByPrivateKey(String data, RSAPrivateKey privateKey)
            throws Exception {
        Cipher cipher = Cipher.getInstance(ALGORITHM_RSA);
        cipher.init(Cipher.ENCRYPT_MODE, privateKey);
        // 模长n转换成字节数
        int modulusSize = privateKey.getModulus().bitLength() / 8;
        // PKCS Padding长度为11字节，所以实际要加密的数据不能要 - 11byte
        int maxSingleSize = modulusSize - 11;
        // 切分字节数组，每段不大于maxSingleSize
        byte[][] dataArray = splitArray(data.getBytes(CHARSET), maxSingleSize);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        // 分组加密，并将加密后的内容写入输出字节流
        for (byte[] s : dataArray) {
            out.write(cipher.doFinal(s));
        }
        // 使用Base64将字节数组转换String类型
        return Base64.getEncoder().encodeToString(out.toByteArray());
    }

    /**
     * @Author: hhp
     * @Description: 公钥解密
     * @Data: 2023/10/23 15:05
     * @param java.lang.String data
     * @param java.security.interfaces.RSAPublicKey publicKey
     * @return: java.lang.String
     * @throws:
     **/
    public static String decryptByPublicKey(String data, RSAPublicKey publicKey)
            throws Exception {
        Cipher cipher = Cipher.getInstance(ALGORITHM_RSA);
        cipher.init(Cipher.DECRYPT_MODE, publicKey);
        // RSA加密算法的模长 n
        int modulusSize = publicKey.getModulus().bitLength() / 8;
        byte[] dataBytes = data.getBytes(CHARSET);
        // 之前加密的时候做了转码，此处需要使用Base64进行解码
        byte[] decodeData = Base64.getDecoder().decode(dataBytes);
        // 切分字节数组，每段不大于modulusSize
        byte[][] splitArrays = splitArray(decodeData, modulusSize);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        for (byte[] arr : splitArrays) {
            out.write(cipher.doFinal(arr));
        }
        return new String(out.toByteArray());
    }

    /**
     * @Author: hhp
     * @Description: 按指定长度切分数组
     * @Data: 2023/10/23 15:05
     * @param byte data
     * @param int len 单个字节数组长度
     * @return: byte[][]
     * @throws:
     **/
    private static byte[][] splitArray(byte[] data, int len) {
        int dataLen = data.length;
        if (dataLen <= len) {
            return new byte[][]{data};
        }
        byte[][] result = new byte[(dataLen - 1) / len + 1][];
        int resultLen = result.length;
        for (int i = 0; i < resultLen; i++) {
            if (i == resultLen - 1) {
                int slen = dataLen - len * i;
                byte[] single = new byte[slen];
                System.arraycopy(data, len * i, single, 0, slen);
                result[i] = single;
                break;
            }
            byte[] single = new byte[len];
            System.arraycopy(data, len * i, single, 0, len);
            result[i] = single;
        }
        return result;
    }

    public static void main(String[] args) throws Exception {
        Map<String, String> keyStringList = RSAUtils.getRSAKeyString(1024);
        String pukString = keyStringList.get(PUBLIC_KEY);
        String prkString = keyStringList.get(PRIVATE_KEY);
        /*String pukString = RSAConstants.RSA_PUBLIC_KEY;
        String prkString = RSAConstants.RSA_PRIVATE_KEY;*/
        log.info("tes公钥:[{}]", pukString);
        log.info("tes私钥:[{}]", prkString);
        // 生成公钥、私钥
        RSAPublicKey puk = RSAUtils.getPublicKey(pukString);
        RSAPrivateKey prk = RSAUtils.getPrivateKey(prkString);
        String message = "{\"message\":\"test\",\"name\":\"test\"}";
        String encryptedMsg = RSAUtils.encryptByPublicKey(message, puk);
        String decryptedMsg = RSAUtils.decryptByPrivateKey(encryptedMsg, prk);
        log.info("未加密内容:[{}]", message);
        log.info("公钥加密内容:[{}]", encryptedMsg);
        log.info("私钥解密内容:[{}]", decryptedMsg);
    }

}
